/**
 * @file 坐标转换服务
 * @description 提供不同坐标系之间的转换功能
 * @author Assistant
 * @date 2024-02-05
 */

import { Injectable } from '@nestjs/common';
import { LoggerService } from '../../../common/services/logger.service';

@Injectable()
export class CoordinateService {
  /**
   * 坐标转换常量：扩大的PI值
   * @private
   */
  private readonly x_PI = (3.14159265358979324 * 3000.0) / 180.0;
  
  /**
   * 坐标转换常量：PI值
   * @private
   */
  private readonly PI = 3.14159265358979324;
  
  /**
   * 坐标转换常量：地球长半轴
   * @private
   */
  private readonly a = 6378245.0;
  
  /**
   * 坐标转换常量：偏心率平方
   * @private
   */
  private readonly ee = 0.00669342162296594323;

  /**
   * 当前活跃的请求数量
   * @private
   */
  private activeRequests: number = 0;
  
  /**
   * 最大并发请求数限制
   * @description 根据百度地图API个人开发者并发要求设置，低于30
   * @private
   */
  private maxConcurrent: number = 25;

  constructor(private readonly logger: LoggerService) {}

  /**
   * 获取并发许可
   * @description 通过简单的计数器限制并发请求数量，避免超过API限制
   * @returns Promise<void> 获取到许可后解析的Promise
   * @private
   */
  private async _acquireLimit(): Promise<void> {
    while (this.activeRequests >= this.maxConcurrent) {
      await new Promise(resolve => setTimeout(resolve, 50)); // 每50ms检查一次
    }
    this.activeRequests++;
  }

  /**
   * 释放并发许可
   * @description 减少活跃请求计数，确保并发控制正常工作
   * @returns void
   * @private
   */
  private _releaseLimit(): void {
    if (this.activeRequests > 0) {
      this.activeRequests--;
    }
  }

  /**
   * 进行坐标系转换
   * @description 提供坐标系之间的转换服务，当前使用备用转换方法实现
   * @param lng 经度
   * @param lat 纬度
   * @param from 源坐标系代码
   * @param to 目标坐标系代码
   * @returns 转换后的坐标对象，包含经纬度
   */
  public async convertCoordinates(lng: number, lat: number, from: number, to: number): Promise<{ lng: number, lat: number }> {
    // 暂时直接使用备用转换方法
    return this.backupConvertCoordinates(lng, lat, from, to);
    
    /* 暂时注释掉官方接口调用
    await this._acquireLimit();
    try {
      const response = await axios.get('https://api.map.baidu.com/geoconv/v1/', {
        params: {
          coords: `${lng},${lat}`,
          from,
          to,
          ak: process.env.BAIDU_API_KEY || 'YOUR_API_KEY'
        }
      });
      if (response.data.status === 0 && response.data.result && response.data.result.length > 0) {
        const result = response.data.result[0];
        return { lng: result.x, lat: result.y };
      } else {
        this.logger.error('百度坐标转换接口返回错误', response.data);
        // 如果返回错误信息中包含并发限制相关内容，则调用备用转换方法
        if (response.data.trace && response.data.trace.message && response.data.trace.message.includes('并发量')) {
          this.logger.warn('检测到并发限制错误，使用备用转换方法。');
          return this.backupConvertCoordinates(lng, lat, from, to);
        }
      }
    } catch (error) {
      const errorMsg = String(error);
      this.logger.error('调用百度坐标转换接口异常', errorMsg);
      // 如果异常信息包含并发限制提示，则使用备用转换方法
      if (errorMsg.includes('并发') || errorMsg.includes('concurrent')) {
        this.logger.warn('检测到并发限制异常，转而使用备用转换方法。');
        return this.backupConvertCoordinates(lng, lat, from, to);
      }
    } finally {
      this._releaseLimit();
    }
    return { lng, lat };
    */
  }

  /**
   * 备用坐标转换方法
   * @description 当调用百度官方接口失败时，使用本地转换公式进行坐标系转换
   * @param lng 经度
   * @param lat 纬度
   * @param from 源坐标系代码
   * @param to 目标坐标系代码
   * @returns 转换后的坐标对象，包含经纬度
   */
  public backupConvertCoordinates(lng: number, lat: number, from: number, to: number): { lng: number, lat: number } {
    // 目前仅支持从WGS84 (1) 转换到 BD09 (5)
    if (from === 1 && to === 5) {
      if (this._outOfChina(lng, lat)) {
        return { lng, lat };
      }
      const dLat = this._transformLat(lng - 105.0, lat - 35.0);
      const dLng = this._transformLng(lng - 105.0, lat - 35.0);
      const radLat = (lat / 180.0) * this.PI;
      let magic = Math.sin(radLat);
      magic = 1 - this.ee * magic * magic;
      const sqrtMagic = Math.sqrt(magic);
      const deltaLat = (dLat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtMagic) * this.PI);
      const deltaLng = (dLng * 180.0) / (this.a / sqrtMagic * Math.cos(radLat) * this.PI);
      const gcjLng = lng + deltaLng;
      const gcjLat = lat + deltaLat;
      const z = Math.sqrt(gcjLng * gcjLng + gcjLat * gcjLat) + 0.00002 * Math.sin(gcjLat * this.x_PI);
      const theta = Math.atan2(gcjLat, gcjLng) + 0.000003 * Math.cos(gcjLng * this.x_PI);
      const bd_lng = z * Math.cos(theta) + 0.0065;
      const bd_lat = z * Math.sin(theta) + 0.006;
      return { lng: bd_lng, lat: bd_lat };
    } else {
      // 不支持其他转换类型时，返回原始坐标
      return { lng, lat };
    }
  }

  /**
   * 计算纬度偏移量
   * @description 用于WGS84到GCJ02坐标系转换过程中的纬度偏移计算
   * @param x 相对于中国中心点(105°E, 35°N)的经度偏移
   * @param y 相对于中国中心点的纬度偏移
   * @returns 计算后的纬度偏移量
   * @private
   */
  private _transformLat(x: number, y: number): number {
    let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
    ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
    ret += (20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0;
    ret += (160.0 * Math.sin(y / 12.0 * this.PI) + 320 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0;
    return ret;
  }

  /**
   * 计算经度偏移量
   * @description 用于WGS84到GCJ02坐标系转换过程中的经度偏移计算
   * @param x 相对于中国中心点(105°E, 35°N)的经度偏移
   * @param y 相对于中国中心点的纬度偏移
   * @returns 计算后的经度偏移量
   * @private
   */
  private _transformLng(x: number, y: number): number {
    let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
    ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
    ret += (20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0;
    ret += (150.0 * Math.sin(x / 12.0 * this.PI) + 300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0;
    return ret;
  }

  /**
   * 判断坐标是否位于中国范围外
   * @description 检查给定的经纬度坐标是否在中国大陆及周边地区范围外
   * @param lng 经度
   * @param lat 纬度
   * @returns 如果坐标在中国范围外返回true，否则返回false
   * @private
   */
  private _outOfChina(lng: number, lat: number): boolean {
    return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
  }
} 